import { Rule, SchematicContext, Tree, apply, url, applyTemplates, move, chain, mergeWith } from '@angular-devkit/schematics';
import * as path from 'path';
import { strings } from '@angular-devkit/core'
import { plural } from 'pluralize';
import * as ts from 'typescript'

//不用默认导出函数。您也可以拥有多个规则工厂
//每个文件。
export function nestCmsHandlebarsTemplate(options: any): Rule {
  return (_tree: Tree, _context: SchematicContext) => {
    // 从选项中获取name属性，并赋值给entityName
    const entityName = options.name;    //获取name参数
    const title = options.path;

    // 定义要应用的模板规则,有点像gulp
    const sourceTemplateRules = apply(
      // 指定模板文件所在的目录
      url('./files/src'),
      [
        // 应用模板，将传入的选项传递给模板
        applyTemplates({
          entityName,
          title,
          plural,
          ...strings, //向模板中传入一些方法
        }),
        // 移动生成的文件到目标目录中
        move(path.normalize('src'))
      ]
    )

    const viewsTemplateRules = apply(
      url('./files/views'),
      [
        applyTemplates({
          entityName,
          title,
          plural,
          ...strings,
        }),
        move(path.normalize('views'))
      ]
    )

    // 返回一个chain，将模板规则 与文件系统合并
    return chain([
      mergeWith(sourceTemplateRules),
      mergeWith(viewsTemplateRules),
      updateAdminModule(entityName),
      updateSharedModule(entityName),
    ])
  };
}

/*
url 指定模板文件的源路径，通常是本地的文件，./files指的是当前文件相对路径的files目录所有文件
applyTemplates 应用模板引擎，将模板文件与上下文 数据结合生成目标文件
move 移动生成的文件到目标 目录中
apply 应用一系列规则到文件树中，转换后的文件树

mergeWith 将生成的文件与项目中的文件树进行合并
chain 将多个规则按顺序进行串联执行
*/


/**
 * Rule 是一个函数，它接受一个Tree对象，并且返回一个新的Tree对象。用于定义文件系统的变更更规则
 *
 * SchematicContext 提供了有关当前运行中的原理图上下文信息和工厂，比如说日志记录和任务调度
 *
 * Tree 是一个虚拟的文件系统，用于暂存和记录对实际文件系统的更新，直到提交时才应用
 * */

// 此方法用于更新src/admin/admin.module.ts文件
function updateAdminModule(entityName: string) {
  //返回一个函数，接受Tree作为参数
  return (tree: Tree) => {    // 此处返回的是一个schematics的规则函数
    // 1. 定义要修改的文件路径
    const adminModulePath = 'src/admin/admin.module.ts';
    // 2. 读取并解析文件的内容，为TS源文件
    const sourceFile = getSourceFile(tree, adminModulePath);
    // 3. 如果成功找到了源文件
    if (sourceFile) {
      // 获取实体类的名称（用于代码），和破折号形式（用于文件名）
      const { classifyiedName, dasherizedName } = getClassifyiedAndDasherizeName(entityName);
      // 定义更新操作
      const updates: Array<ts.TransformerFactory<ts.SourceFile>> = [
        addImportToModule(`${classifyiedName}Controller`, `./controller/${dasherizedName}.controller`),    // 修改 import {} from 'xx'
        addItemToModuleArray('controllers', `${classifyiedName}Controller`)  // 修改 providers:[RoleController,xxController]
      ]
      // 应用更新，并保存变更到 src/admin/admin.module.ts 文件
      applyTransformationAndSave(tree, adminModulePath, sourceFile, updates);
    }
    return tree
  }
}

// 读取并解析源文件为ts的源文件对象
function getSourceFile(tree: Tree, filePath: string): ts.SourceFile {
  // 读取指定路径的文件内容，并转换为字符串
  const content = tree.read(filePath)?.toString('utf-8');
  // 使用ts创建源文件对象
  return ts.createSourceFile(filePath, content!, ts.ScriptTarget.Latest, true);
}

function getClassifyiedAndDasherizeName(name: string) {
  return {
    classifyiedName: strings.classify(name),    //获取类名形式
    dasherizedName: strings.dasherize(name),   //获取 破折号形式
  }
}

// 在文件中添加 import 导入语句
function addImportToModule(importName: string, importPath: string): ts.TransformerFactory<ts.SourceFile> {
  // 返回一个转换工厂函数，用于添加导入语句
  // TransformerFactory是一个高阶函数，会接受一个TransformationContext并返回另一个处理SourceFile的函数
  return (_context: ts.TransformationContext) => (rootNode: ts.SourceFile) => {
    // 找到文件中的最后一个 import 语句
    const lastImport = rootNode.statements.filter(ts.isImportDeclaration).pop();
    // 创建一个新的 import 语句
    // modifiers,importClause,moduleSpecifier,attributes
    const newImport = ts.factory.createImportDeclaration(
      undefined,
      ts.factory.createImportClause(    // import type
        false,
        undefined,
        ts.factory.createNamedImports([
          ts.factory.createImportSpecifier(
            false,
            undefined,
            ts.factory.createIdentifier(importName)
          ),
        ]),
      ),
      ts.factory.createStringLiteral(importPath)    // from 'xxx'
    )

    const updateStatements = ts.factory.createNodeArray([
      ...rootNode.statements.slice(0, rootNode.statements.indexOf(lastImport!) + 1),
      newImport,
      ...rootNode.statements.slice(rootNode.statements.indexOf(lastImport!) + 1),
    ])
    return ts.factory.updateSourceFile(rootNode, updateStatements) as ts.SourceFile;
  }
}

function addItemToModuleArray(arrayName: string, itemName: string): ts.TransformerFactory<ts.SourceFile> {
  return (context: ts.TransformationContext) => (rootNode: ts.SourceFile) => {
    // 定义访问器函数，用于递归的循环节点
    function visitor(node: ts.Node): ts.Node {
      // 判断节点是否是属性数值
      if (
        ts.isPropertyAssignment(node) &&
        ts.isIdentifier(node.name) &&
        node.name.text === arrayName &&
        ts.isArrayLiteralExpression(node.initializer)
      ) {
        const elements = [...node.initializer.elements.map(ele => ele.getText()), itemName]
        // 返回更新后的数组属性节点
        return ts.factory.updatePropertyAssignment(
          node,
          node.name,
          ts.factory.createArrayLiteralExpression(elements.map(elestr => {
            return ts.factory.createIdentifier(elestr)
          }))
        )
      }
      return ts.visitEachChild(node, visitor, context);
    }
    return ts.visitNode(rootNode, visitor) as ts.SourceFile;
  }
}

// 应用变更并保存文件
function applyTransformationAndSave(
  tree: Tree,
  filePath: string,
  sourceFile: ts.SourceFile,
  transformations: Array<ts.TransformerFactory<ts.SourceFile>>
) {
  // 应用变更并获取更新后的文件
  const updatedSourceFile = ts.transform(sourceFile, transformations).transformed[0];
  // 将更新后的文件内容写入指定的路径
  tree.overwrite(filePath, ts.createPrinter().printFile(updatedSourceFile));
}

function updateSharedModule(entityName: string){
  return (tree: Tree) => {
    const sharedModulePath = 'src/shared/shared.module.ts';
    const sourceFile = getSourceFile(tree, sharedModulePath);
    if (sourceFile) {
      const { classifyiedName, dasherizedName } = getClassifyiedAndDasherizeName(entityName);
      const updates: Array<ts.TransformerFactory<ts.SourceFile>> = [
        addImportToModule(`${classifyiedName}Service`, `./services/${dasherizedName}.service`),
        addImportToModule(`${classifyiedName}Entity`, `./entities/${dasherizedName}.entity`),
        addItemToModuleArray('providers', `${classifyiedName}Service`),
        addItemToModuleArray('exports', `${classifyiedName}Service`),
        addToMethodArray('forFeature', `${classifyiedName}Entity`)
      ]
      applyTransformationAndSave(tree, sharedModulePath, sourceFile, updates);
    }
    return tree
  }
}

// 给方法调用添加 信息 TypeOrmModule.forFeature([UserEntity, TestEntity, RoleEntity, GameEntity]),
function addToMethodArray(methodName: string, resourceName: string): ts.TransformerFactory<ts.SourceFile> {
  return (context: ts.TransformationContext) => (rootNode: ts.SourceFile) => {
      function visitor(node: ts.Node): ts.Node {
        if (
          ts.isCallExpression(node) &&
          ts.isPropertyAccessExpression(node.expression) &&
          node.expression.name.text === methodName &&
          node.arguments.length === 1 &&
          ts.isArrayLiteralExpression(node.arguments[0])
        ) {
          const elements = [...node.arguments[0].elements, ts.factory.createIdentifier(resourceName)]
          // 将新数组，作为参数传递
          return ts.factory.updateCallExpression(
            node,
            node.expression,
            node.typeArguments,
            [ts.factory.createArrayLiteralExpression(elements)]
          )
        }
        return ts.visitEachChild(node, visitor, context);
      }
      return ts.visitNode(rootNode, visitor) as ts.SourceFile;
  }
}